#include <ArduinoOTA.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <FS.h>
#include <SHTSensor.h>
#include <Servo.h>
#include <SparkFun_Qwiic_Humidity_AHT20.h>  //Click here to get the library: http://librarymanager/All#Qwiic_Humidity_AHT20 by SparkFun
#include <Ticker.h>
#include <WiFiClient.h>
#include <WiFiUdp.h>
#include <Wire.h>

#include "./EepromCheck/EepromCheck.h"
#include "AlarmList.h"

// #include <ArduinoJson.h>
#include <Arduino_JSON.h>

static WiFiClient espClient;
#include <AliyunIoTSDK.h>
#include <ArduinoJson.h>
AliyunIoTSDK iot;

// #define PRODUCT_KEY "a1GW2GFBxc4"
// #define DEVICE_NAME "alarm001"
// #define DEVICE_SECRET "8515f5f79955bdb223124580f0a1a702"
#define PRODUCT_KEY "a1tshjvH2ak"
#define DEVICE_NAME "Device_1"
#define DEVICE_SECRET "c2efba91ec07c77b725c89b721e9ffcd"
#define REGION_ID "cn-shanghai"

// const char *ssid = "MERCURY_7DD6";
// const char *password = "15965319310";
// const char *ssid = "Honor V9";
// const char *password = "12345678909";
// const char *ssid = "MERCURY_9A87";
// const char *password = "rm888888";
// const char *ssid = "MyIoTHome";
// const char *password = "12345678909";
const char *ssid = "Xiaomi_0969";
const char *password = "sds88685151";
const char *ApName = "Esp8266";
const char *ApPassword = "12345678909";

const char *HtmlRootPageFileURL = "/index.html";   // 网页主页在文件系统中的路径
const char *AlarmListDataFileURL = "/alarm.data";  // 闹钟数据文件路径
const char *LogFileURL = "/log.txt";               // 日志文件路径
const char *SensorFileURL = "/sensorData.txt";     // 传感器文件路径

#define EEPROM_DATA_START_ADDR 4
#define EEPROM_VERSION_START_ADDR 0

#define ENCODE_VERSION(major, minor, patch, dev) \
    ((major << 24) | (minor << 16) | (patch << 8) | dev)

#define VERSION ENCODE_VERSION(1, 0, 1, 15)

/* ***************************** Start 函数声明 ***************************** */
void openLight(int state = 0);
void closeLight(int state = 0);
void handleRoot();
void handleUpdateTime();
void handleGetMcuStatus();
void handleToggleLight();
void handleSetAlarm();
void handleRemoveAlarm();
void handleShowAlarm();
void handleChangeRange();
void handleNotFound();
void tickerTimeStamp_Callback();
void TaskHandleLightStatus();
void InitAlarmList();
void TaskSaveAlarmList();
int8_t TaskFetchAlarmList();
void TaskUpdateAlarmList();
void handleShowLog();
void handleMcuReset();
void TaskReadSensorData();
void handleOTAUpdate();
void handleShowSensorData();
void handleDeleteSensorDataFile();
/* ****************************** End 函数声明 ****************************** */

typedef struct QueueBuff
{
    uint16_t len;
    uint16_t pos;
    float buff[25];
} QueueBuffTypeDef;

QueueBuff temperBuff = {25, 0, 100.0};
QueueBuff humidifyBuff = {25, 0, 100.0};

enum _LightStatusEnum
{
    lightStatusOff = 0,
    lightStatusOn,
};

enum _LightCtrlEnum
{
    lightCtrlOpen = 0,
    lightCtrlClose,
};

struct _MainStatus
{
    uint8_t servoPin;                    ///< 舵机管脚
    uint8_t servo1PowPin;                ///< 舵机MOS引脚
    uint8_t humanDetectionPin;           ///< 人体检测管脚
    uint8_t openLightOptVal;             ///< 开灯时的舵机角度[0-180]
    uint8_t closeLightOptVal;            ///< 关灯时的舵机角度[0-180]
    int16_t lightAdcValue;               ///< 当前检测到的光敏电阻上的电压值
    int16_t lightAdcJudgeValue;          ///< 光敏电阻上电压的阈值,大于等于此值表示灯亮
    uint8_t OptFunTblLen;                ///< OptFunTbl的长度
    uint8_t tickFlag;                    ///<
    uint8_t humanDetection : 1;          ///< 人体检测
    uint8_t currLightStatus : 1;         ///< 根据传感器检测的当前灯的状态
    uint8_t lightControl : 1;            ///< 控制灯亮灭的控制位
    uint8_t lightControlFlag : 1;        ///< 按下改变灯亮灭的标志位
    uint8_t alarmUpdateFlag : 1;         ///< 闹铃链表更新标志位
    uint8_t OTAUpdateFlag : 1;           ///< OTA升级
    uint8_t manualChangedLightFlag : 1;  ///< 控制物理开关来控制灯的标志位
    float temperature;                   ///< 温度数据
    float humidity;                      ///< 湿度数据
} gStatus = {0};                         // = {D7, D8, 135, 55, 0, 600, sizeof(OptFunTbl) / sizeof(OptFunType), 0, 0, 0};

ESP8266WebServer server(80);

Servo servo;
Ticker currTimeTicker;
SHTSensor sht;
// To use a specific sensor instead of probing the bus use this command:
// SHTSensor sht(SHTSensor::SHT3X);
EepromCheck<uint32_t> eepromVersion;
EepromCheck<struct _MainStatus> eepromMainStatus;
AlarmList alarm = AlarmList();
CurrentTime currTime;
AHT20 aht;

const AlarmType InitAlarmEventList[] = {
    {0, 1, 7, 30, -1, OptIndex_OpenLight, NULL, NULL},
    {0, 2, 7, 30, -1, OptIndex_OpenLight, NULL, NULL},
    {0, 3, 7, 30, -1, OptIndex_OpenLight, NULL, NULL},
    {0, 4, 7, 30, -1, OptIndex_OpenLight, NULL, NULL},
    {0, 5, 7, 30, -1, OptIndex_OpenLight, NULL, NULL},
    {0, 6, 8, 45, -1, OptIndex_OpenLight, NULL, NULL},
    {0, 7, 8, 45, -1, OptIndex_OpenLight, NULL, NULL},
    {0, 1, 8, 35, -1, OptIndex_CloseLight, NULL, NULL},
    {0, 2, 8, 35, -1, OptIndex_CloseLight, NULL, NULL},
    {0, 3, 8, 35, -1, OptIndex_CloseLight, NULL, NULL},
    {0, 4, 8, 35, -1, OptIndex_CloseLight, NULL, NULL},
    {0, 5, 8, 35, -1, OptIndex_CloseLight, NULL, NULL},
};

#define HTML_BUFF_SIZE (2 * 1024)
char str[HTML_BUFF_SIZE];

/**
 * @brief  解析版本号为字符串形式
 * @param  v: 版本号
 * @retval 版本号字符串
 */
char *parseVersion(uint32_t v)
{
    if ((v & 0xff) != 0)
    {
        sprintf(str, "%d.%d.%d.dev%d", (v >> 24) & 0xff, (v >> 16) & 0xff, (v >> 8) & 0xff,
                v & 0xff);
    }
    else
    {
        sprintf(str, "%d.%d.%d", (v >> 24) & 0xff, (v >> 16) & 0xff, (v >> 8) & 0xff);
    }
    return str;
}

/**
 * @brief  添加日志
 * @param  log: 日志内容
 * @retval None
 */
void WriteLog(char *log)
{
    File f = SPIFFS.open(LogFileURL, "a");
    if (!f)
    {
        Serial.printf("File open error! File name is \"%s\" [a]!", LogFileURL);
    }
    f.printf("T:%d-%d-%d [%d] %d:%d:%d,Event:{%s}\n", currTime.year, currTime.month, currTime.date,
             currTime.day, currTime.hour, currTime.minute, currTime.second, log);
    f.close();
}

int8_t ReadLog(String &log)
{
    File f = SPIFFS.open(LogFileURL, "r");
    if (!f)
    {
        Serial.printf("File open error! File name is \"%s\" [r]!", LogFileURL);
    }
    log = f.readString();
    f.close();
}

/**
 * @brief  添加温湿度日志
 * @param  None
 * @retval None
 */
void WriteSensorData()
{
    File f = SPIFFS.open(SensorFileURL, "a");
    if (!f)
    {
        Serial.printf("File open error! File name is \"%s\" [a]!", SensorFileURL);
    }
    f.printf("Time:%d-%d-%d [%d] %d:%d:%d,T:%.1f,H:%.1f\n", currTime.year, currTime.month,
             currTime.date, currTime.day, currTime.hour, currTime.minute, currTime.second,
             gStatus.temperature, gStatus.humidity);
    f.close();
}

/**
 * @brief
 * @param  None
 *   @arg
 * @retval None
 */
int8_t ReadSensorData(String &data, uint32_t len)
{
    File f = SPIFFS.open(SensorFileURL, "r");
    String t;

    if (!f)
    {
        Serial.printf("File open error! File name is \"%s\" [r]!", LogFileURL);
    }
    while (len--)
    {
        t = f.readStringUntil('\n');
        if (t == "")
            break;
        else
            data += t + "\n";
    }
    f.close();
}

void openLight(int state)
{
    gStatus.lightControl = lightCtrlOpen;
    gStatus.lightControlFlag = true;
    WriteLog("open light");
    if (state == OptParam_Auto)
    {
        gStatus.manualChangedLightFlag = false;
    }
}

void closeLight(int state)
{
    gStatus.lightControl = lightCtrlClose;
    gStatus.lightControlFlag = true;
    WriteLog("close light");
    if (state == OptParam_Auto)
    {
        gStatus.manualChangedLightFlag = false;
    }
}

void saveDataToEEPROM()
{
    uint8_t *p;
    uint16_t i, len = sizeof(struct _MainStatus);
    uint32_t version;

    version = VERSION;
    eepromVersion.save(EEPROM_VERSION_START_ADDR, version);
    eepromMainStatus.save(EEPROM_DATA_START_ADDR, gStatus);
}

/**
 * @brief  若检测到设备不是第一次使用,则读取EEPROM中的数据
 * @param  None
 * @retval 设备第一次使用? 1表示第一次
 */
int8_t fetchDataFromEEPROM()
{
    uint8_t *p;
    uint16_t i, len = sizeof(struct _MainStatus);
    uint32_t version;
    int8_t retval = 0;

    eepromVersion.fetch(EEPROM_VERSION_START_ADDR, version);
    if (version == VERSION)
    {
        eepromMainStatus.fetch(EEPROM_DATA_START_ADDR, gStatus);
    }
    else
    {
        retval = 1;
    }

    return retval;
}

/**
 * @brief  判断当前灯的状态
 * @param  None
 * @retval None
 */
void TaskAdjustLightStatus(void)
{
    uint8_t val;
    gStatus.lightAdcValue = analogRead(A0);
    if (gStatus.lightAdcValue >= gStatus.lightAdcJudgeValue)  // 灯亮状态
    {
        val = lightStatusOn;
    }
    else
    {
        val = lightStatusOff;
    }

    // 如果状态改变,并且不处于控制阶段,则可以判断当前是人为按下灯的开关来开或者关灯
    if (val != gStatus.currLightStatus)
    {
        gStatus.currLightStatus = val;
        if (gStatus.lightControlFlag == false)
        {
            gStatus.manualChangedLightFlag = true;
        }
    }
    // gStatus.lightAdcValue = analogRead(A0);
    // if (gStatus.lightAdcValue >= gStatus.lightAdcJudgeValue)  // 灯亮状态
    // {
    //   gStatus.currLightStatus = lightStatusOn;
    // }
    // else
    // {
    //   gStatus.currLightStatus = lightStatusOff;
    // }
}

/**
 * @brief  采集人体感应器的数据
 * @note   检测到引脚跳变后会进行1000ms的消抖处理
 * @param  None
 * @retval None
 */
void TaskAdjustHuman(void)
{
    static uint16_t cnt = 0;

    // gStatus.humanDetection = digitalRead(gStatus.humanDetectionPin);

    if (digitalRead(gStatus.humanDetectionPin) == 0)
    {
        cnt = 0;
        gStatus.humanDetection = false;
    }
    else
    {
        cnt++;
        if (cnt >= 50)
        {
            cnt = 50;
            gStatus.humanDetection = true;
        }
    }

    if (gStatus.humanDetection == true && gStatus.currLightStatus == lightStatusOff)
    {
        if (currTime.hour >= 17 && currTime.hour <= 23)
        {
            if (gStatus.manualChangedLightFlag == false)
            {
                gStatus.manualChangedLightFlag = true;
                openLight(OptParam_Manual);
            }
        }
    }
}

void toggleLEDCallback(JsonVariant p)
{
    if (gStatus.currLightStatus == lightStatusOn)
    {
        closeLight(OptParam_Remote);
    }
    else if (gStatus.currLightStatus == lightStatusOff)
    {
        openLight(OptParam_Remote);
    }
}

void updateDateCallback(JsonVariant p)
{
    AliyunIoTSDK::send("LedState", gStatus.currLightStatus);
    AliyunIoTSDK::send("Temperature", gStatus.temperature);
    AliyunIoTSDK::send("Humidity", gStatus.humidity);
    AliyunIoTSDK::send("Data_Adc", gStatus.lightAdcValue);
}

void setup(void)
{
    uint8_t i = 0;
    Wire.begin(D5, D6);
    Serial.begin(115200);
    // IPAddress localIp(192, 168, 43, 1);
    // IPAddress gateWay(192, 168, 0, 1);
    // IPAddress subNet(255, 255, 255, 0);
    // IPAddress dns(192, 168, 0, 1);
    // WiFi.config(localIp, gateWay, subNet, dns, dns);
    // WiFi.softAPConfig(localIp, gateWay, subNet);
    WiFi.softAP(ApName, ApPassword);
    // WiFi.mode(WIFI_STA);
    WiFi.setAutoConnect(true);
    WiFi.setAutoReconnect(true);
    WiFi.begin(ssid, password);

    while (WiFi.status() != WL_CONNECTED && i++ < 60)
    {
        delay(1000);
        Serial.print(".");
    }
    Serial.println("\r\nwifi connected ");
    Serial.println(WiFi.localIP());

    currTimeTicker.attach_ms(1000, tickerTimeStamp_Callback);
    server.on("/", handleRoot);
    server.on("/updateTime", handleUpdateTime);
    server.on("/setAlarm", handleSetAlarm);
    server.on("/removeAlarm", handleRemoveAlarm);
    server.on("/toggleLight", handleToggleLight);
    server.on("/showAlarm", handleShowAlarm);
    server.on("/changeRange", handleChangeRange);
    server.on("/getMcuStatus", handleGetMcuStatus);
    server.on("/showLog", handleShowLog);
    server.on("/mcuReset", handleMcuReset);
    server.on("/deleteLogFile", handleDeleteLogFile);
    server.on("/otaUpdate", handleOTAUpdate);
    server.on("/showSensorData", handleShowSensorData);
    server.on("/deleteSensorDataFile", handleDeleteSensorDataFile);
    server.onNotFound(handleNotFound);
    server.begin();

    { /* OTA初始化代码 */
        ArduinoOTA.onStart([]() {
            String type;
            if (ArduinoOTA.getCommand() == U_FLASH)
            {
                type = "sketch";
            }
            else
            {  // U_SPIFFS
                type = "filesystem";
            }

            // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using
            SPIFFS.end();
            Serial.println("Start updating " + type);
        });
        ArduinoOTA.onEnd([]() { Serial.println("\nEnd"); });
        ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) {
            Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
        });
        ArduinoOTA.onError([](ota_error_t error) {
            Serial.printf("Error[%u]: ", error);
            if (error == OTA_AUTH_ERROR)
            {
                Serial.println("Auth Failed");
            }
            else if (error == OTA_BEGIN_ERROR)
            {
                Serial.println("Begin Failed");
            }
            else if (error == OTA_CONNECT_ERROR)
            {
                Serial.println("Connect Failed");
            }
            else if (error == OTA_RECEIVE_ERROR)
            {
                Serial.println("Receive Failed");
            }
            else if (error == OTA_END_ERROR)
            {
                Serial.println("End Failed");
            }
        });
        ArduinoOTA.begin();
    }

    if (!SPIFFS.begin())
    {
        Serial.println("SPIFFS begin Error!");
    }
    // Dir dir = SPIFFS.openDir("/");
    // while (dir.next())
    // {
    //     String fileName = dir.fileName();
    //     size_t fileSize = dir.fileSize();
    //     Serial.printf("FS File:%s,size:%d\n", fileName.c_str(), fileSize);
    // }

    EEPROM.begin(512);

    // Serial.println(parseVersion(VERSION));

    if (fetchDataFromEEPROM())
    {
        // 首次开机
        initVariables();
    }
    else
    {
        // 非首次开机
    }
    if (TaskFetchAlarmList() < 0)
    {
        Serial.println("Fetch alarm list failed!\tThe default list will be used!");
        InitAlarmList();  // 设置初始闹钟
    }
    currTime = CurrentTime();
    pinMode(gStatus.servo1PowPin, OUTPUT);
    pinMode(gStatus.humanDetectionPin, INPUT);  // 人体检测模块引脚

    // RTClib RTC;
    // DateTime ti = RTC.now();
    // Serial.printf("\nRTC sec:%d unix:%d\n", 1, ti.unixtime());

    if (aht.begin() == false)
    {
        Serial.println("SHT sensor init failed");
    }

    AliyunIoTSDK::begin(espClient, PRODUCT_KEY, DEVICE_NAME, DEVICE_SECRET, REGION_ID);

    // 绑定属性回调
    AliyunIoTSDK::bindData("LedState", toggleLEDCallback);
    AliyunIoTSDK::bindData("Data_Adc", updateDateCallback);
    AliyunIoTSDK::bindData("Humidity", updateDateCallback);
    AliyunIoTSDK::bindData("Temperature", updateDateCallback);

    WriteLog("MCU start run!");
}
unsigned long lastMsMain = 0;
void loop(void)
{
    static uint32_t ts = 0, eventCnt = 0;
    AlarmType *p;
    int16_t i;
    size_t len;
    float temp = 0.0;

    if (gStatus.tickFlag)
    {
        // currTime.second += currTime.tickCnt;
        gStatus.tickFlag = 0;
        p = alarm.getHead();
        len = alarm.getLength();
        Serial.printf("\n\nLength:%d\r\n", len);
        for (i = 0; i < len; i++)
        {
            if (p != NULL)
            {
                Serial.printf("index:%2d\tdate:%2d day:%1d hour:%2d min:%2d opt:%1d\n", i, p->date,
                              p->day, p->hour, p->minute, p->optIndex);
            }
            else
            {
                Serial.printf("index:%d Null\r\n", i);
            }
            p = p->next;
        }
        // return;
        currTime.updateTime();
        // 遍历闹钟事件
        alarm.compare(currTime);

        // if (currTime.minute % 5 == 0 && currTime.second == 0)
        // {
        //     WriteSensorData();
        // }

        Serial.printf("This time is:\t%d/%d/%d [%d] %d:%d:%d {%d}%d\r\n", currTime.year,
                      currTime.month, currTime.date, currTime.day, currTime.hour, currTime.minute,
                      currTime.second, currTime.updateWay, UPDATE_TIME_WAY_BY_RTC);
        Serial.printf(
            "ADC:%d\tDigital:%d\r\n"
            "temperature:%.2fC\tHumidity:%.2f%RH\r\n",
            gStatus.lightAdcValue, gStatus.humanDetection, gStatus.temperature, gStatus.humidity);
    }
    else
    {
        if ((millis() - ts) >= 5)  // 5ms执行一次
        {
            ts = millis();

            eventCnt++;
            if ((eventCnt % 4) == 0)
            {
                TaskAdjustLightStatus();  // 判断当前灯的状态:开或关
                TaskAdjustHuman();        // 检测人体
                TaskHandleLightStatus();  // 根据控制位判断是否需要执行操作
                TaskUpdateAlarmList();    // 根据标志位判断是否需要更新闹铃数据
                TaskReadSensorData();     // 读取传感器检测到的数据
            }
            if ((eventCnt % 100) == 0)
            {
                // if (temperBuff.pos)
                {
                    temp = 0.0;
                    for (i = 0; i < temperBuff.len; i++)
                    {
                        temp += temperBuff.buff[i];
                    }
                    gStatus.temperature = temp / temperBuff.len;
                    // temperBuff.pos = 0;
                }
                // if (humidifyBuff.pos)
                {
                    temp = 0.0;
                    for (i = 0; i < humidifyBuff.len; i++)
                    {
                        temp += humidifyBuff.buff[i];
                    }
                    gStatus.humidity = temp / humidifyBuff.len;
                    // humidifyBuff.pos = 0;
                }
            }
            if ((eventCnt % 400) == 0)
            {
                AliyunIoTSDK::send("LedState", gStatus.currLightStatus);
                AliyunIoTSDK::send("Temperature", gStatus.temperature);
                AliyunIoTSDK::send("Humidity", gStatus.humidity);
                AliyunIoTSDK::send("Data_Adc", gStatus.lightAdcValue);
            }
        }
    }

    server.handleClient();
    AliyunIoTSDK::loop();
    if (gStatus.OTAUpdateFlag)
    {
        ArduinoOTA.handle();
    }
}

void initVariables()
{
    gStatus.servoPin = D1;
    gStatus.servo1PowPin = D0;
    gStatus.humanDetectionPin = D7;
    gStatus.openLightOptVal = 135;
    gStatus.closeLightOptVal = 55;
    gStatus.lightAdcValue = 0;
    gStatus.lightAdcJudgeValue = 600;
    gStatus.OptFunTblLen = sizeof(OptFunTbl) / sizeof(OptFunType);
    gStatus.humanDetection = 0;
}

void handleRoot()
{
    File myFile;
    myFile = SPIFFS.open(HtmlRootPageFileURL, "r");
    if (myFile)
    {
        Serial.printf("Root. SPIFFS Success! File size is:%d\n", myFile.size());
        server.send(200, "text/html", myFile.readString());
        myFile.close();
    }
    else
    {
        Serial.println("Root. SPIFFS Error!");
        server.send(200, "text/plain", "Please check your SPIFFS!");
    }
}

void handleOTAUpdate()
{
    String message = "{\"operate\":\"Delete Log File.\",\"state\":\"failed\",\"err info\":\"\"}";

    gStatus.OTAUpdateFlag = 1;
    server.send(200, "text/plain", message);
}

void handleUpdateTime()
{
    String message;
    JSONVar obj;
    int ts = 0;

    Serial.println(server.arg(0));
    obj = JSON.parse(server.arg(0));
    ts = (int)obj["current time"];
    currTime.updateTimeByTimeStamp(ts);

    obj = NULL;
    obj["Update time response"]["state"] = "success";
    obj["Update time response"]["err info"] = "";
    message = JSON.stringify(obj);
    server.send(200, "text/plain", message);
}

void handleGetMcuStatus()
{
    JSONVar obj;
    String message;

    snprintf(str, HTML_BUFF_SIZE,
             "<p>%d-%d-%d</p><p>week:%d</p><p>%d:%d:%d</p><p>温度:%.1f,湿度:%.1f</p>",
             currTime.year, currTime.month, currTime.date, currTime.day, currTime.hour,
             currTime.minute, currTime.second, gStatus.temperature, gStatus.humidity);
    obj["status"]["mcu time"] = str;
    if (gStatus.currLightStatus == lightStatusOn)
    {
        obj["status"]["light state"] = "Light Status: On.";
    }
    else if (gStatus.currLightStatus == lightStatusOff)
    {
        obj["status"]["light state"] = "Light Status: Off.";
    }
    obj["status"]["operate range"]["open light"] = gStatus.openLightOptVal;
    obj["status"]["operate range"]["close light"] = gStatus.closeLightOptVal;
    message = JSON.stringify(obj);
    server.send(200, "text/plain", message);
}

void handleToggleLight()
{
    String message = "{\"msg\":\"Toggle Light success!\"}";
    Serial.println(message);
    if (gStatus.currLightStatus == lightStatusOn)
    {
        closeLight(OptParam_Remote);
    }
    else if (gStatus.currLightStatus == lightStatusOff)
    {
        openLight(OptParam_Remote);
    }
    server.send(200, "text/plain", message);
}

void handleSetAlarm()
{
    String message = "success", errInfo;
    AlarmType *pAlarm;
    AlarmType *p;
    uint8_t state = 0;
    const char *s;
    JSONVar obj;
    uint32_t i, len;
    int ret;

    // 创建一个新闹钟结构体
    pAlarm = (AlarmType *)malloc(sizeof(AlarmType));
    if (pAlarm == NULL)
    {
        Serial.println("Memory malloc failed![handleSetAlarm]");
        return;
    }
    Serial.println(server.arg(0));
    obj = JSON.parse(server.arg(0));
    pAlarm->date = 0;
    pAlarm->day = (int)obj["Add alarm"]["day"];
    s = (const char *)obj["Add alarm"]["time"];
    pAlarm->hour = (s[0] - '0') * 10 + (s[1] - '0');
    pAlarm->minute = (s[3] - '0') * 10 + (s[4] - '0');
    pAlarm->optIndex = (int)obj["Add alarm"]["operate"];
    pAlarm->optIndex -= 1;

    if (pAlarm->optIndex >= gStatus.OptFunTblLen)
    {
        state = 1;
        errInfo = "Err: no this function!";
    }
    else if (pAlarm->day > 7)
    {
        state = 1;
        errInfo = "Err: Day more than 7!";
    }
    else if (pAlarm->hour > 23)
    {
        state = 1;
        errInfo = "Err: Hour more than 23!";
    }
    else if (pAlarm->minute > 60)
    {
        state = 1;
        errInfo = "Err: Minute more than 60!";
    }
    else if (pAlarm->optIndex >= OptIndex_Limit)
    {
        state = 1;
        errInfo = "Err: optIndex more than OptIndex_Limit!";
    }
    else
    {
        /* 验证通过,将其添加到闹钟链表中 */
        if ((ret = alarm.add(pAlarm)) != 0)
        {
            if (ret == -1)
            {
                state = 1;
                errInfo = "Err: There is a same alarm!";
            }
            else if (ret == -2)
            {
                state = 1;
                errInfo = "Err: Memory malloc failed!";
            }
        }
    }
    obj = NULL;
    if (state == 0)
    {
        obj["Add alarm response"]["state"] = "success";
        obj["Add alarm response"]["err info"] = "";
        gStatus.alarmUpdateFlag = true;
    }
    else if (state == 1)
    {
        obj["Add alarm response"]["state"] = "failed";
        obj["Add alarm response"]["err info"] = errInfo;
        free(pAlarm);
    }
    message = JSON.stringify(obj);
    server.send(200, "text/plain", message);
}

void handleRemoveAlarm()
{
    String message, errInfo;
    AlarmType *p, *last, pAlarm;
    uint8_t state = 0;
    int32_t i;
    const char *s;
    JSONVar obj;

    Serial.println(server.arg(0));
    obj = JSON.parse(server.arg(0));
    pAlarm.day = (int)obj["Remove alarm"]["day"];
    s = (const char *)obj["Remove alarm"]["time"];
    pAlarm.hour = (s[0] - '0') * 10 + (s[1] - '0');
    pAlarm.minute = (s[3] - '0') * 10 + (s[4] - '0');
    s = (const char *)obj["Remove alarm"]["operate"];
    for (i = 0; i < OptIndex_Limit; i++)
    {
        if (strcmp(s, OptFunTbl[i].info) == 0)
        {
            break;
        }
    }
    pAlarm.optIndex = i;
    if (i >= OptIndex_Limit)
    {
        state = 1;
        errInfo = "Err: There is no such operation!";
    }
    else if (pAlarm.day > 7)
    {
        state = 1;
        errInfo = "Err: Day more than 7!";
    }
    else if (pAlarm.hour > 23)
    {
        state = 1;
        errInfo = "Err: Hour more than 23!";
    }
    else if (pAlarm.minute > 60)
    {
        state = 1;
        errInfo = "Err: Minute more than 60!";
    }
    else
    {
        if (alarm.remove(pAlarm) == -1)
        {
            state = 1;
            errInfo = "Err: Remove alarm error!";
        }
    }
    obj = NULL;
    if (state == 0)
    {
        obj["Remove alarm response"]["state"] = "success";
        obj["Remove alarm response"]["err info"] = "";
        gStatus.alarmUpdateFlag = true;
    }
    else if (state == 1)
    {
        obj["Remove alarm response"]["state"] = "failed";
        obj["Remove alarm response"]["err info"] = errInfo;
    }
    message = JSON.stringify(obj);

    server.send(200, "text/plain", message);
}

void handleShowAlarm()
{
    JSONVar obj;
    String message;
    uint8_t i;
    uint32_t alarmCnt = 0, len = 0;
    AlarmType *p;

    p = alarm.getHead();
    len = alarm.getLength();

    if (len == 0)
    {
        obj["alarm"] = "None";
    }
    else
    {
        for (i = 0; i < len; i++)
        {
            if (p != NULL)
            {
                obj["alarm"][alarmCnt]["day"] = p->day;
                obj["alarm"][alarmCnt]["hour"] = p->hour;
                obj["alarm"][alarmCnt]["minute"] = p->minute;
                obj["alarm"][alarmCnt]["operate"] = OptFunTbl[p->optIndex].info;
                alarmCnt++;
            }
            p = p->next;
        }
    }

    message = JSON.stringify(obj);
    server.send(200, "text/plain", message);
}

void handleChangeRange()
{
    String message = "{\"msg\":\"Change operate range!\"}";
    const char *s;
    int opt = 0, val = 0;
    JSONVar obj;

    Serial.println(server.arg(0));
    obj = JSON.parse(server.arg(0));
    opt = (int)obj["operate"];
    val = (int)obj["value"];
    switch (opt)
    {
    case 1:  // open light
        Serial.printf("Operate: open light:%d\r\n", val);
        gStatus.openLightOptVal = val;
        openLight(OptParam_Test);
        break;
    case 2:  // close light
        Serial.printf("Operate: close light:%d\r\n", val);
        gStatus.closeLightOptVal = val;
        closeLight(OptParam_Test);
        break;
    default:
        break;
    }

    // obj["operate range"]["open light"] = gStatus.openLightOptVal;
    // obj["operate range"]["close light"] = gStatus.closeLightOptVal;
    // message = JSON.stringify(obj);
    saveDataToEEPROM();
    server.send(200, "text/plain", message);
}

void handleShowLog()
{
    String message;

    ReadLog(message);

    server.send(200, "text/plain", message);
}

void handleShowSensorData()
{
    String message;
    JSONVar obj;
    uint32_t len = 50;

    Serial.println(server.arg(0));
    obj = JSON.parse(server.arg(0));
    len = (int)obj["len"];

    ReadSensorData(message, len);

    server.send(200, "text/plain", message);
}

void handleDeleteLogFile()
{
    String message;
    if (SPIFFS.remove(LogFileURL))
    {
        message = "{\"operate\":\"Delete Log File.\",\"state\":\"success\",\"err info\":\"\"}";
    }
    else
    {
        message = "{\"operate\":\"Delete Log File.\",\"state\":\"failed\",\"err info\":\"\"}";
    }

    server.send(200, "text/plain", message);
}

void handleDeleteSensorDataFile()
{
    String message;
    if (SPIFFS.remove(SensorFileURL))
    {
        message =
            "{\"operate\":\"Delete Sensor Data File.\",\"state\":\"success\",\"err info\":\"\"}";
    }
    else
    {
        message =
            "{\"operate\":\"Delete Sensor Data File.\",\"state\":\"failed\",\"err info\":\"\"}";
    }
    server.send(200, "text/plain", message);
}

void handleMcuReset()
{
    String message = "{\"operate\":\"Reset MCU\"}";

    WriteLog("Reset MCU");
    server.send(200, "text/plain", message);

    delay(500);
    ESP.restart();
}

void handleNotFound()
{
    String message = "File Not Found\n\n";
    message += "URI: ";
    message += server.uri();
    message += "\nMethod: ";
    message += (server.method() == HTTP_GET) ? "GET" : "POST";
    message += "\nArguments: ";
    message += server.args();
    message += "\n";

    for (uint8_t i = 0; i < server.args(); i++)
    {
        message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
    }

    server.send(404, "text/plain", message);
}

void tickerTimeStamp_Callback()
{
    currTime.increaseTimeStamp();
    gStatus.tickFlag = 1;
}

/**
 * @brief  根据控制位来执行操作
 * @param  None
 * @retval None
 */
void TaskHandleLightStatus()
{
    static uint8_t state = 0, lastState = 0, cnt = 0, servoVal = 0, optCnt = 0, currServoVal = 0;

    switch (state)
    {
    case 0:  // 初始状态,等待开关灯
        if (gStatus.lightControlFlag == true)
        {
            state = 1;
            // gStatus.lightControlFlag = false;
            optCnt = 0;
        }
        break;
    case 1:  // 判断是否需要执行操作
        cnt = 0;
        if (gStatus.lightControl == lightCtrlOpen && gStatus.currLightStatus == lightStatusOff)
        {
            digitalWrite(gStatus.servo1PowPin, HIGH);
            servo.attach(gStatus.servoPin);
            Serial.println("open");
            servoVal = gStatus.openLightOptVal;
            currServoVal = servo.read();
            lastState = 2;
            if (currServoVal < servoVal)
            {
                state = 5;
            }
            else
            {
                state = 6;
            }
        }
        else if (gStatus.lightControl == lightCtrlClose && gStatus.currLightStatus == lightStatusOn)
        {
            digitalWrite(gStatus.servo1PowPin, HIGH);
            servo.attach(gStatus.servoPin);
            Serial.println("close");
            servoVal = gStatus.closeLightOptVal;
            currServoVal = servo.read();
            lastState = 2;
            if (currServoVal < servoVal)
            {
                state = 5;
            }
            else
            {
                state = 6;
            }
        }
        else
        {
            state = 4;
        }
        break;
    case 2:  // 执行完后的延时操作
        cnt++;
        if (cnt > 10)
        {
            servo.write(90);
            state = 3;
            cnt = 0;
        }
        break;
    case 3:
        cnt++;
        if (cnt > 88)
        {  // 50*20ms
            if ((gStatus.lightControl == lightCtrlOpen &&
                 gStatus.currLightStatus == lightStatusOff) ||
                (gStatus.lightControl == lightCtrlClose &&
                 gStatus.currLightStatus == lightStatusOn))
            {
                if (optCnt++ < 5)
                {  // 尝试操作5次,如果还不行,则放弃
                    state = 1;
                }
                else
                {
                    WriteLog("Toggle light error!");
                    state = 4;
                }
            }
            else
            {
                servoVal = 90;
                currServoVal = servo.read();
                if (currServoVal < servoVal)
                {
                    state = 5;
                    lastState = 4;
                }
                else
                {
                    state = 6;
                    lastState = 4;
                }
            }
        }
        break;
    case 4:
        state = 0;  // 完成
        digitalWrite(gStatus.servo1PowPin, LOW);
        servo.detach();
        gStatus.lightControlFlag = false;
        break;
    case 5:  // 小
        currServoVal += 2;
        if (currServoVal > servoVal)
        {
            currServoVal = servoVal;
            state = lastState;
        }
        servo.write(currServoVal);
        break;
    case 6:  // 大
        currServoVal -= 2;
        if (currServoVal < servoVal)
        {
            currServoVal = servoVal;
            state = lastState;
        }
        servo.write(currServoVal);
        break;
    default:
        break;
    }
}

/**
 * @brief  根据程序内置的闹钟事件初始化闹铃事件链表
 * @param  None
 * @retval None
 */
void InitAlarmList()
{
    uint16_t i, len;
    AlarmType *p;
    int ret = 0;

    len = sizeof(InitAlarmEventList) / sizeof(AlarmType);
    for (i = 0; i < len; i++)
    {
        p = (AlarmType *)malloc(sizeof(AlarmType));
        p->date = InitAlarmEventList[i].date;
        p->day = InitAlarmEventList[i].day;
        p->hour = InitAlarmEventList[i].hour;
        p->minute = InitAlarmEventList[i].minute;
        p->optIndex = InitAlarmEventList[i].optIndex;
        p->cnt = InitAlarmEventList[i].cnt;
        p->next = NULL;
        p->pre = NULL;
        ret = alarm.add(p);
    }
}

void TaskSaveAlarmList()
{
    AlarmType *p;
    JSONVar obj;
    size_t len, i, alarmCnt = 0;
    /* 格式
    {
      "Alarm":{
        "1":[
          {"hour":7,"minute":5,"operate":0},
          {"hour":8,"minute":25,"operate":1},
        ],
        "2":[
          {"hour":7,"minute":5,"operate":0},
          {"hour":8,"minute":25,"operate":1},
        ],
        "3":[
          {"hour":7,"minute":5,"operate":0},
          {"hour":8,"minute":25,"operate":1},
        ],
      }
    }
    */
    p = alarm.getHead();
    len = alarm.getLength();

    if (len == 0)
    {
        obj["alarm"] = "None";
    }
    else
    {
        for (i = 0; i < len; i++)
        {
            if (p != NULL)
            {
                obj["alarm"][alarmCnt]["day"] = p->day;
                obj["alarm"][alarmCnt]["hour"] = p->hour;
                obj["alarm"][alarmCnt]["minute"] = p->minute;
                obj["alarm"][alarmCnt]["operate"] = p->optIndex;
                alarmCnt++;
            }
            p = p->next;
        }
    }

    File f = SPIFFS.open(AlarmListDataFileURL, "w");
    if (!f)
    {
        Serial.println("Root. SPIFFS Error! The file name is \"alarm.data\" [w]!");
        return;
    }
    String jsonString = JSON.stringify(obj);
    f.print(jsonString);
    f.close();
}

/**
 * @brief  读取Flash中的闹铃数据
 * @param  None
 * @retval 读取成功与否.[0:成功;-1:没有闹铃数据文件;-2:闹铃数据文件中的数据错误]
 */
int8_t TaskFetchAlarmList()
{
    String fileString;
    uint16_t day, i, len;
    AlarmType *p;

    File f = SPIFFS.open(AlarmListDataFileURL, "r");
    if (!f)
    {
        Serial.println("Root. SPIFFS Error! The file name is \"alarm.data\" [r]!");
        return -1;
    }
    fileString = f.readString();
    f.close();
    JSONVar obj = JSON.parse(fileString);
    if (JSON.typeof(obj) == "undefined")
    {
        Serial.println("Parsing input failed! ");
        return -2;
    }

    JSONVar listObj = obj["alarm"];
    if (listObj != "None")
    {
        len = listObj.length();
        for (i = 0; i < len; i++)
        {
            p = (AlarmType *)malloc(sizeof(AlarmType));
            if (p == NULL)
            {
                Serial.println("Memory malloc error!");
                break;
            }
            p->day = (int)listObj[i]["day"];
            p->hour = (int)listObj[i]["hour"];
            p->minute = (int)listObj[i]["minute"];
            p->optIndex = (int)listObj[i]["operate"];
            alarm.add(p);
        }
    }
}

void TaskUpdateAlarmList()
{
    static uint32_t ts = 0;
    if ((millis() - ts) > 10000)
    {  // 每30s检测一次
        ts = millis();
        if (gStatus.alarmUpdateFlag == true)
        {
            gStatus.alarmUpdateFlag = false;
            TaskSaveAlarmList();
        }
    }
}

void TaskReadSensorData()
{
    if (aht.available() == true)
    {
        humidifyBuff.buff[humidifyBuff.pos] = aht.getHumidity();
        humidifyBuff.pos = (humidifyBuff.pos + 1) % humidifyBuff.len;
        temperBuff.buff[temperBuff.pos] = aht.getTemperature();
        temperBuff.pos = (temperBuff.pos + 1) % temperBuff.len;
    }
    else  // 设备掉线
    {
    }
}
